﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Drawing;
using Silvermoon.Core;
using System.Diagnostics;
using Silvermoon.Controls.Base;

namespace Silvermoon.Controls
{
    public class TouchEventArgs : CancelEventArgs
    {
        #region fields
        private const int velocitySize = 10;
        public const float LongFlickVelocity = 0.1f;  // was 0.2f before

        private Point[] velocities = new Point[velocitySize];
        private int[] durations = new int[velocitySize];
        private int velocityIndex = 0;
        private int maxVelocity = 0;
        public static new readonly TouchEventArgs Empty = new TouchEventArgs();

        private const int MinMotionDistance = 3;
        private const double motionThreshold = 32;
        private const int tooLongPressedThreshold = 2200;
        private int lastTick;
        private int currentTick;
        private int startTick;

        private Point startPosition;
        private Point position;
        private Point lastPos;
        private int downTick;
        private bool motionDetected = false;

        internal Control ControlToPan;
        #endregion
        #region ctor

        internal TouchEventArgs()
            : base()
        {
            startTick = Environment.TickCount;
        }

        #endregion
        #region props

        public bool DoubleTap { get; internal set; }

        internal int CurrentTick { get { return currentTick; } }

        public bool FocusSet { get; set; }

        /// <summary>
        /// Gets whether the display is touched.
        /// </summary>
        public bool IsTouched { get; internal set; }

        /// <summary>
        /// Gets the direction in to which it is most probably attended to be scrolled:
        /// </summary>
        public PanDirection Direction
        {
            get
            {
                PanDirection dir = PanDirection.None;
                if (AbsX > AbsY)
                {
                    int dx = Dx;
                    if (dx < 0) dir |= PanDirection.Left;
                    else if (dx > 0) dir |= PanDirection.Right;
                }
                if (AbsY > AbsX)
                {
                    int dy = Dy;
                    if (dy < 0) dir |= PanDirection.Up;
                    else if (dy > 0) dir |= PanDirection.Down;
                }
                return dir;
            }
        }

        /// <summary>
        /// Gets the position where the display was first touched.
        /// </summary>
        public Point StartPosition
        {
            get { return startPosition; }
            internal set
            {
                PanMode = false;
                motionDetected = false;
                downTick = Environment.TickCount;
                startPosition = value;
                lastPos = value;
                position = value;
            }
        }

        /// <summary>
        /// Gets the current position.
        /// </summary>
        public Point Position
        {
            get { return position; }
            internal set
            {
                UpdateDuration();

                lastPos = position;
                position = value;
            }
        }

        public bool AllowDoubleTap { get; set; }

        /// <summary>
        /// Gets how long it took from one event to another (e.g. from on MouseDown to next MouseDown, or from MouseDown to MouseUp).
        /// </summary>
        internal int LastEventDuration { get { return currentTick - lastTick; } }

        /// <summary>
        /// Gets how long the display is touched.
        /// </summary>
        internal int Duration { get { return currentTick - downTick; } }

        /// <summary>
        /// Gets whether motion was already detected.
        /// </summary>
        internal bool HasMotionDetected { get { return motionDetected; } }

        /// <summary>
        /// Gets whether motion is detected.
        /// </summary>
        internal bool IsMotionDetected
        {
            get
            {
                if (motionDetected == false) motionDetected = DetectMotion();
                return motionDetected;
            }
        }

        internal bool IsLongPressed;

        /// <summary>
        /// Gets whether to perform OnScroll or OnMove
        /// </summary>
        internal bool PanMode { get; set; }


        internal bool IsTooTongPressed
        {
            get
            {
                return Duration > tooLongPressedThreshold;
            }
        }

        private bool DetectMotion()
        {
            int dx = position.X - startPosition.X;
            int dy = position.Y - startPosition.Y;
            double distance = Math.Sqrt(dx * dx + dy * dy);
            return distance > motionThreshold;
        }

        public int Dx
        {
            get { return position.X - startPosition.X; }
        }

        public int Dy
        {
            get { return position.Y - startPosition.Y; }
        }

        public int AbsX { get { return Math.Abs(Dx); } }
        public int AbsY { get { return Math.Abs(Dy); } }

        /// <summary>
        /// Gets the horizontal velocity in pixel/ms.
        /// </summary>
        public float HorizontalVelocity
        {
            get
            {
                return GetHorizVelocity();
                //return (float)(position.X - lastPos.X) / (float)(lastTick - currentTick);
            }
        }

        internal int DeltaTick
        {
            get { return lastTick - currentTick; }
        }

        /// <summary>
        /// Gets the vertical velocity in pixel/ms.
        /// </summary>
        public float VerticalVelocity
        {
            get
            {
                return GetVertVelocity();
                //return (float)(position.Y - lastPos.Y) / (float)(lastTick - currentTick);
            }
        }

        /// <summary>
        /// Gets the velocity  in pixel/ms.
        /// </summary>
        public double Velocity
        {
            get { return Math.Sqrt(HorizontalVelocity * HorizontalVelocity + VerticalVelocity * VerticalVelocity); }
        }


        /// <summary>
        /// Gets the distance in pixel that has been paned.
        /// </summary>
        public double Distance
        {
            get
            {
                int a = position.X - lastPos.X;
                int b = position.Y - lastPos.Y;
                return Math.Sqrt(a * a + b * b);
            }
        }


        public bool HorizontalMotion { get { return AbsX > AbsY && AbsY > MinMotionDistance; } }
        public bool VerticalMotion { get { return AbsX < AbsY && AbsX > MinMotionDistance; } }

        #endregion
        #region methods

        internal void UpdateDuration()
        {
            int current = Environment.TickCount;
            lastTick = currentTick;
            currentTick = current;
            if (lastTick!=0) UpdateVelocity();
        }

        internal void UpdateVelocity()
        {
            velocities[velocityIndex] = GetVelocity();
            durations[velocityIndex] = currentTick - lastTick;
            maxVelocity = Math.Min(velocitySize, maxVelocity + 1);
            velocityIndex++;
            if (velocityIndex >= velocitySize) velocityIndex = 0;
        }

        private Point GetVelocity()
        {
            int x = position.X - lastPos.X;
            int y = position.Y - lastPos.Y;
            return new Point(x, y);
        }

        private float GetHorizVelocity()
        {
            int value = 0;
            int duration = 0;
            for (int i = 0; i < maxVelocity; i++)
            {
                value += velocities[i].X;
                duration -= durations[i];
            }
            return (float)value / duration;
        }

        private float GetVertVelocity()
        {
            int value = 0;
            int duration = 0;
            for (int i = 0; i < maxVelocity; i++)
            {
                value += velocities[i].Y;
                duration -= durations[i];
            }
            return (float)value / (float)duration;
        }


        public Point GetDeltaPos()
        {
            return new Point(Dx, Dy);
        }

        public Point GetAbsDeltaPos()
        {
            return new Point(AbsX, AbsY);
        }

        #endregion
    }

    [Flags]
    public enum PanDirection
    {
        None = 0,
        Left = 1,
        Right = 2,
        Up = 4,
        Down = 8,

        Vertical = Up | Down,
        Horizontal = Left | Right
    }
}
